<?php

namespace app\service;

use app\core\Constant;
use app\core\exception\BusinessException;
use app\core\Request;
use app\core\Service;
use app\core\util\Encryp;
use app\core\util\http;
use app\core\util\Token;
use app\model\User;
use app\model\SysDept;
use app\model\SysMenu;
use app\model\SysPermission;
use app\model\SysRole;
use app\model\SysRoleMenu;
use app\model\SysRolePerm;
use app\model\SysUser;
use app\model\SysUserPost;
use app\model\SysUserRole;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Log;

/**
 * @extends Service<UserService>
 */
class UserService extends Service
{
    /**
     * 用户登录
     * @param string $username 帐号
     * @param string $password 密码
     * @param string|null $company_name 企业名称
     * @return string
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function login(string $username, string $password)
    {
        $user = SysUser::where([
            'user_name' => $username
        ])->find();

        if (!$user) {
            throw new BusinessException('帐号不存在');
        }


        if ($user['password'] !== Encryp::password($password)) {
            throw new BusinessException('密码不正确');
        }

        if ($user['status'] != Constant::STATUS_NORMAL) {
            throw new BusinessException('帐号已停用');
        }
        // 保存最后登录时间
        $user->save([
            'login_date' => date('Y-m-d H:i:s', time()),
            'login_ip' => Request::invoke()->ip()
        ]);

        $userInfo = $user->toArray();

        // 生成 Token
        $token = Token::invoke()
            ->setUserId($userInfo['user_id'])
            ->setUserInfo($userInfo)
            ->genToken();

        return $token;
    }

    /**
     * @param $userId
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function refreshUserInfo($userId)
    {
        $user = SysUser::find($userId);
        if (!$user) {
            throw new BusinessException('用户不存在');
        }

        $roles = $this->getRoles($user->user_id);
        $isSuperAdmin = false;
        $user['roles'] = $roles['role_keys'];

        if (in_array('admin', $user['roles'])) {
            $isSuperAdmin = true;
        }

        $user['permissions'] = $this->getPermissions($user['user_id'], $isSuperAdmin);
        $userInfo = $user->toArray();

        // 刷新用户信息
        Token::invoke()
            ->setUserId($userId)
            ->refreshUserInfo($userInfo);

        return $userInfo;
    }

    /**
     * 根据 ID 查找用户
     * @param int $userId
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getUserInfo(int $userId): array
    {
        $user = SysUser::find($userId);

        if (!$user) {
            throw new BusinessException('用户不存在');
        }

        $user['role_ids'] = SysUserRole::where('user_id', $userId)->column("role_id");
        $user['post_ids'] = SysUserPost::where('user_id', $userId)->column('post_id');

        return $user->toArray();
    }

    /**
     * 查询用户角色和数据权限
     * @param int $userId
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function getRoles(int $userId): array
    {
        $data = [
            'role_keys' => [],
            'roles' => [],
        ];
        $role_ids = SysUserRole::where('user_id', $userId)->field('role_id')->buildSql();
        $roles = SysRole::where('role_id', "exp", "in $role_ids")->select()->toArray();
        foreach ($roles as $role) {
            $data['role_keys'][] = $role['role_key'];
            $data['roles'][] = [
                'role_id' => $role['role_id'],
                'data_scope' => $role['data_scope']
            ];
        }
        return $data;
    }

    public function getPermissions(int $userId, $isSuperAdmin = false): array
    {
        if ($isSuperAdmin) {
            return SysPermission::where('perms', '<>', null)
                ->where('perms', '<>', null)
                ->column('perms');
        }
        $role_ids = SysUserRole::where('user_id', $userId)->field('role_id')->buildSql();
        $permission_ids = SysRolePerm::where('role_id', 'exp', "in $role_ids")
            ->field('permission_id')
            ->buildSql();
        return SysPermission::where('permission_id', "exp", "in $permission_ids")
            ->where('perms', '<>', null)
            ->column('perms');
    }

    /**
     * 用户信息脱敏感
     * @param array $user
     * @return array
     */
    public function toSafe(array $user): array
    {
        // 排除密码字段
        if (isset($user['password'])) unset($user['password']);
        // 手机号脱敏处理
        return $user;
    }

    public function getUserList($page = 1, $limit = 10, $searchQuery = null)
    {
        $list = SysUser::scope(['data'])->where(function ($q) use ($searchQuery) {
            if (!empty($searchQuery['user_name'])) {
                $q = $q->where('user_name', 'like', "%" . $searchQuery['user_name'] . "%");
            }
            if (!empty($searchQuery['nick_name'])) {
                $q = $q->where('nick_name', 'like', "%" . $searchQuery['nick_name'] . "%");
            }
            if (!empty($searchQuery['user_id']) && $searchQuery['user_id'] != 1) {
                $q = $q->where('user_id', $searchQuery['user_id']);
            }
            if (!empty($searchQuery['company_name'])) {
                $q = $q->where('company_name', 'like', "%" . $searchQuery['company_name'] . "%");
                unset($searchQuery['dept_id']);
            }

            if (!empty($searchQuery['company_name'])) {
                $dept_name = $searchQuery['company_name'];
                $w[] = ['dept_name', 'like', "%" . $dept_name . "%"];
                $deptInfo = SysDept::where($w)->find();
                if ($deptInfo) {
                    $deptInfo = $deptInfo->toArray();
                    $searchQuery['dept_id'] = $deptInfo['dept_id'];
                }
            }

            if (!empty($searchQuery['role_id'])) {
                $q = $q->where('role_id', $searchQuery['role_id']);
            }
            if (!empty($searchQuery['status'])) {
                $q = $q->where('status', $searchQuery['status']);
            }
            if (!empty($searchQuery['mobile'])) {
                $q = $q->where('phonenumber', $searchQuery['mobile']);
            }
            if (!empty($searchQuery['create_time'])) {
                $time_arr = explode("#", $searchQuery['create_time']);
                if (count($time_arr) > 0) {
                    $q = $q->whereTime('create_time', 'between', $time_arr);
                }
            }
            if (!empty($searchQuery['dept_id'])) {
                $dept_id = $searchQuery['dept_id'];
                $sql = SysDept::where(function ($q1) use ($dept_id) {
                    $q1->whereOr('dept_id', $dept_id)
                        ->whereOr(function ($q2) use ($dept_id) {
                            $q2->whereFindInSet('ancestors', $dept_id);
                        });

                })->field('dept_id')->buildSql();
                $q->whereOr("dept_id", "exp", "in $sql");
            }
        });

        $total = $list->count();
        $list = $list->order('user_id desc')->page($page, $limit)->select()->bindAttr('dept', [
            'dept_name'
        ])->toArray();
        return [
            'total' => $total,
            'list' => $list
        ];
    }

    public function save($data, $id = null)
    {
        if (!is_array($data['role_ids'])) {
            $data['role_id'] = $data['role_ids'];
        }
        $saveData = [
            "dept_id" => 1,
            "role_id" => $data['role_id'],
            "user_name" => $data['user_name'],
            'nick_name' => $data['nick_name'],
            'email' => $data['email'] ?? null,
            'phonenumber' => $data['phonenumber'] ?? null,
            'sex' => $data['sex'] ?? 0,
            'avatar' => '/assets/img/avatar.jpg',
            'password' => Encryp::password($data['password']),
            'status' => $data['status'] ?? Constant::STATUS_NORMAL,
            'remark' => $data['remark'] ?? null,
            'company_name' => $data['company_name'] ?? '',
            'is_main' => $data['is_main'] ?? 0,
        ];

        Db::startTrans();
        try {
            // 密码留空不修改
            if (!empty($id)) {
                if (empty($data['password'])) {
                    unset($saveData['password']);
                }
                $user = SysUser::scope(['data'])->where('user_id', $id)->find();
                if (!$user) {
                    throw new BusinessException('用户不存在');
                }
                if ($saveData['user_name'] != $user['user_name']) {
                    $this->checkUserNameUnique($saveData['user_name']);
                }
                $user->save($saveData);
                // 清空关联角色和岗位
                SysUserRole::where('user_id', $user->user_id)->delete();
                SysUserPost::where('user_id', $user->user_id)->delete();
            } else {
                $this->checkUserNameUnique($saveData['user_name']);

                $user = SysUser::create($saveData);
                if (empty($user)) {
                    throw new BusinessException('创建失败');
                }
            }
            // 关联角色
            if (!SysUserRole::create([
                'user_id' => $user->user_id,
                'role_id' => $data['role_id'],
            ])) {
                throw new BusinessException('创建失败');
            }
            // 关联岗位
            $postIds = $data['post_ids'] ?? [];
            if (count($postIds) > 0) {
                foreach ($postIds as $postId) {
                    if (!SysUserPost::create([
                        'user_id' => $user->user_id,
                        'post_id' => $postId
                    ])) {
                        throw new BusinessException('创建失败');
                    }
                }
            }
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            throw new BusinessException($e->getMessage());
        }

        return true;
    }

    public function del($userId)
    {
        $user = SysUser::find($userId);
        if (!$user) {
            throw new BusinessException('用户不存在');
        }
        $user_roles = SysUserRole::where('user_id', $userId)->column('role_id');
        Db::startTrans();
        try {
            $user->delete();
            // 删除角色/岗位关联
            SysUserPost::where("user_id", $userId)->delete();
            SysUserRole::where('user_id', $userId)->delete();
            Db::commit();
        } catch (\Exception $e) {
            DB::rollback();
            throw new BusinessException($e->getMessage());
        }
    }

    public function checkUserNameUnique($userName)
    {
        if (SysUser::where('user_name', $userName)->find()) {
            throw new BusinessException('用户已存在，请更换登录帐号');
        }
    }

    public function select($type = 'list', $page = 1, $limit = 10, $searchQuery = null, $user_ids = [])
    {
        if ($type == 'list') {
            $list = SysUser::where('status', Constant::STATUS_NORMAL)
                ->where(function ($q) use ($searchQuery) {
                    if (!empty($searchQuery['user_name'])) {
                        $q = $q->where('user_name', 'like', "%" . $searchQuery['user_name'] . "%");
                    }
                    if (!empty($searchQuery['status'])) {
                        $q = $q->where('status', $searchQuery['status']);
                    }
                    if (!empty($searchQuery['mobile'])) {
                        $q = $q->where('phonenumber', $searchQuery['mobile']);
                    }
                    if (!empty($searchQuery['create_time'])) {
                        $time_arr = explode("#", $searchQuery['create_time']);
                        if (count($time_arr) > 0) {
                            $q = $q->whereTime('create_time', 'between', $time_arr);
                        }
                    }
                    if (!empty($searchQuery['dept_id'])) {
                        $dept_id = $searchQuery['dept_id'];
                        $sql = SysDept::where(function ($q1) use ($dept_id) {
                            $q1->whereOr('dept_id', $dept_id)
                                ->whereOr(function ($q2) use ($dept_id) {
                                    $q2->whereFindInSet('ancestors', $dept_id);
                                });
                        })->field('dept_id')->buildSql();
                        $q->whereOr("dept_id", "exp", "in $sql");
                    }
                });

            $total = $list->count();
            $list = $list
                ->field('user_id, user_name, dept_id')
                ->order('user_id desc')->page($page, $limit)->select()->bindAttr('dept', [
                    'dept_name'
                ])->toArray();
            return [
                'total' => $total,
                'list' => $this->toSafe($list)
            ];
        } else {
            return $this->getUsers($user_ids);
        }
    }

    public function getUsers($user_ids = [])
    {
        $users = SysUser::whereIn('user_id', $user_ids)
            ->field('user_id, user_name')
            ->select()->toArray();
        $users = $this->toSafe($users);
        $list = [];
        foreach ($user_ids as $k => $item) {
            $list[$k] = null;
            foreach ($users as $user) {
                if ($item == $user['user_id']) {
                    $list[$k] = $user;
                }
            }
        }
        return $list;
    }

    private string $appId = 'wxdffa68e85a98fe91';
    private string $secret = '5369555ec1562124900bea2b8aa97a7b';

    public function bindWxUser($code, $userId)
    {
        $reqSesData['js_code'] = $code;
        $reqSesData['appid'] = $this->appId;
        $reqSesData['secret'] = $this->secret;
        $reqSesData['grant_type'] = 'authorization_code';
        $reqSesUrl = 'https://api.weixin.qq.com/sns/jscode2session?' . http_build_query($reqSesData);
        $rspSesStr = Http::invoke()->request("GET", $reqSesUrl, [
            'timeout' => 10,
            'header' => 'Content-type:application/x-www-form-urlencoded',
        ]);
        Log::write("jscode2session:$rspSesStr");
        if ($rspSesStr) $rspSes = json_decode($rspSesStr, true);
        if (empty($rspSes)) {
            return '获取open_id请求失败';
        }
        $openId = data_get($rspSes, 'openid');
        if (!$openId) {
            return data_get($rspSes, 'errmsg', '获取open_id失败');
        }
        $res = SysUser::where('user_id', $userId)->save(['open_id' => $openId]);
        return $res ? '' : '绑定失败';
    }

    public function getAccessToken()
    {
        $accTonKey = "access_token:" . $this->appId;
        $accToken = Cache::get($accTonKey);
        if ($accToken) return $accToken;
        $reqTokData = [
            'grant_type' => 'client_credential',
            'appid' => $this->appId,
            'secret' => $this->secret,
        ];
        $reqTokUrl = 'https://api.weixin.qq.com/cgi-bin/token?' . http_build_query($reqTokData);
        $rspTokStr = Http::invoke()->request("GET", $reqTokUrl, [
            'timeout' => 5,
            'header' => [
                "Content-type: application/json;charset='utf-8'",
                "Accept: application/json",
                "Cache-Control: no-cache",
                "Pragma: no-cache",
            ],
        ]);
        if ($rspTokStr) $rspTok = json_decode($rspTokStr, true);
        if (empty($rspTok)) return '';
        $expiresIn = data_get($rspTok, 'expires_in');
        $accTok = data_get($rspTok, 'access_token');
        Cache::set($accTonKey, $accTok, $expiresIn - 300);
        return $accTok;
    }

    public function getList($page = 1, $limit = 10, $searchQuery = null)
    {
        $w = [];
        if (!empty($searchQuery['nick_name'])) {
            $w[] = ['nick_name', 'like', "%" . $searchQuery['nick_name'] . "%"];
        }
        $list = User::where($w)->page($page, $limit);
        return [
            'total' => $list->count(),
            'list' => $list->select(),
        ];
    }
}